package com.kehutong.payfees.app;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;

import com.kehutong.common.DoveClient;
import com.kehutong.common.TokenService;
import com.kehutong.common.util.Token;
import com.kehutong.payfees.dao.ChargeBillDao;
import com.kehutong.payfees.entity.receipt.ReceiptOrder;
import com.kehutong.payfees.entity.record.Account;
import com.kehutong.payfees.service.ChargeRecordServiceImpl;
import com.kehutong.payfees.service.receipt.ReceiptOrderServiceImpl;
import com.kehutong.payfees.service.record.AccoutServiceImpl;
import com.kehutong.payfees.syn.CallBackReturn;
import com.kehutong.payfees.syn.Locks;
import com.kehutong.payfees.util.BasicClient;
import com.kehutong.payfees.util.JDBCUtil;
import org.coraframework.converter.Converter;
import org.coraframework.converter.util.StringUtil;
import org.coraframework.inject.Inject;
import org.coraframework.json.JSONArray;
import org.coraframework.json.JSONObject;
import org.coraframework.logger.Logger;
import org.coraframework.logger.LoggerFactory;
import org.coraframework.mvc.MvcAction;
import org.coraframework.mvc.http.Bean;
import org.coraframework.mvc.http.ReqMapping;
import org.coraframework.mvc.http.result.Result;
import org.coraframework.orm.Pool;
import org.coraframework.orm.jdbc.JdbcSession;
import org.coraframework.orm.jdbc.Page;
import org.coraframework.orm.jdbc.execute.Condition;
import org.coraframework.orm.jdbc.execute.QueryCondition;
import org.coraframework.orm.jdbc.execute.entity.FindArray;
import org.coraframework.util.Objects;

import com.kehutong.common.PageUtils;
import com.kehutong.common.math.CalculatorUtils;
import com.kehutong.payfees.entity.ChargeAnnex;
import com.kehutong.payfees.entity.ChargeBill;
import com.kehutong.payfees.entity.ChargeBillItem;
import com.kehutong.payfees.entity.record.ChargeRecord;
import com.kehutong.payfees.entity.record.ChargeRecordTemporary;
import com.kehutong.payfees.enums.BillEnableState;
import com.kehutong.payfees.enums.BillOperateType;
import com.kehutong.payfees.enums.BillPaidState;
import com.kehutong.payfees.enums.BillPayMethod;
import com.kehutong.payfees.enums.ChargeAnnexEntityType;
import com.kehutong.payfees.enums.ChargeAnnexType;
import com.kehutong.payfees.enums.ChargeRecordRelType;
import com.kehutong.payfees.enums.WaterSource;
import com.kehutong.payfees.service.ChargeBillAnnexServiceImpl;
import com.kehutong.payfees.service.ChargeBillServiceImpl;
import com.kehutong.payfees.service.ChargeLogServiceImpl;

/**
 * Author:  peter
 * Date:    2020/7/8
 * Function:物业端-缴费模块
 */
@MvcAction
@ReqMapping("/kht-bin/charge")
public class AppChargeAction  {

    private static final Logger logger = LoggerFactory.getLogger(AppChargeAction.class);

    @Inject
    private DoveClient doveClient;
    @Inject
    private ChargeBillServiceImpl chargeBillService;
    @Inject
    private ChargeLogServiceImpl chargeLogService;
    @Inject
    private ChargeBillAnnexServiceImpl chargeBillAnnexService;
    @Inject
    private AccoutServiceImpl accoutService;
    @Inject
    private ChargeRecordServiceImpl chargeRecordService;
    @Inject
    private TokenService tokenService;
    @Inject
    private JdbcSession jdbcSession;
    @Inject
    private BasicClient basicClient;
    @Inject
    private ChargeBillDao chargeBillDao;
    @Inject
    private ReceiptOrderServiceImpl receiptOrderServiceImpl;

    /**
     * 缴费- 数据统计
     */
    @ReqMapping("/bill/statistics")
    public Object billStatistics(JSONObject request) {

        FindArray<ChargeBill> find = jdbcSession.findArray(ChargeBill.class)
                .eq(request, "communityNo")
                .eq("enableState", BillEnableState.ENABLE)
                .eq("deleted", false);

        JDBCUtil.between(request, find,"shouldPayDate");

        List<ChargeBill> bills = find.exe();

        JSONObject result = new JSONObject();

        BigDecimal shouldReceive = chargeBillService.sumTotal(bills);
        result.put("shouldReceive", shouldReceive);

        BigDecimal paid = chargeBillService.sumPaidTotal(bills);
        result.put("paid", paid);

        BigDecimal unpaid = chargeBillService.sumUnPaidTotal(bills);
        result.put("unpaid", unpaid);

        // 收缴率 = 已收费用 ÷ 应收费用 * 100%
        result.put("paidRatio", CalculatorUtils.percentage(paid, shouldReceive));

        return Result.success(result);
    }


    /**
     * 缴费-可查询时间范围
     */
    @ReqMapping("/bill/time_range")
    public Object billTimeRange(Token token) {

        JSONObject result = getBillShouldPayDateTimeRange(token);

        return Result.success(result);
    }

    private JSONObject getBillShouldPayDateTimeRange(Token token) {

        QueryCondition query = Condition.createQuery(false);

        Map<String,Object> rs = chargeBillDao.getShouldPayDate(query);
        JSONObject r = new JSONObject();
        r.put("minDate", rs.get("minDate"));
        r.put("maxDate", rs.get("maxDate"));
        return r;
    }

    /**
     * 小区列表
     */
    @ReqMapping("/community/page")
    public Object pageCommunity(Token token, JSONObject req) throws Exception {

        JSONArray communitys = basicClient.getCommunitys(token, req);

        List<JSONObject> result = communitys.stream()
                .map(c -> {
                    JSONObject json = (JSONObject) c;
                    JSONObject v = new JSONObject();
                    v.put("id", json.getString("id"));
                    v.put("name", json.getString("name"));
                    v.put("area_names", json.getJSONArray("area_names"));
                    v.put("paidRatio", calcPaidRatioByCommunityNo(json.getString("id"), req));

                    return v;
                }).collect(Collectors.toList());

        Page<JSONObject> r = PageUtils.buildPage(req, result);

        return Result.success(r);
    }

    /**
     * 收缴率 = 已收费用 ÷ 应收费用 * 100%
     */
    private BigDecimal calcPaidRatioByCommunityNo(String communityNo, JSONObject request) {

         FindArray<ChargeBill> find = jdbcSession.findArray(ChargeBill.class)
                .eq("communityNo", communityNo)
                .eq("enableState", BillEnableState.ENABLE)
                .eq("deleted", false);

        JDBCUtil.between(request, find, "shouldPayDate");

        List<ChargeBill> bills = find.exe();

        BigDecimal shouldReceive = chargeBillService.sumTotal(bills);

        BigDecimal paid = chargeBillService.sumPaidTotal(bills);

        return CalculatorUtils.percentage(paid, shouldReceive);
    }

    /**
     * 住户列表
     */
    @ReqMapping("/house/page")
    public Object pageHouse(Token token, JSONObject req) throws Exception {

        JSONArray data = basicClient.getHouses(token, req);

        List<JSONObject> list = data.stream()
                .map(item -> {
                    JSONObject house = (JSONObject) item;
                    JSONObject v = new JSONObject();
                    v.put("id", house.getString("id"));
                    v.put("name", house.getString("name"));
                    v.put("buildingName", house.getString("buildName"));
                    v.put("floorName", house.getString("floorName"));
                    v.put("unitName", house.getString("unitName"));

                     FindArray<ChargeBill> find = jdbcSession.findArray(ChargeBill.class)
                            .eq("houseNo", house.getString("id"))
                            .eq("enableState", BillEnableState.ENABLE)
                            .eq("deleted", false);

                    JDBCUtil.between(req, find, "shouldPayDate");
                    List<ChargeBill> bills = find.exe();

                    BigDecimal unpaid = chargeBillService.sumUnPaidTotal(bills);
                    v.put("unpaid", unpaid);

                    return v;
                }).collect(Collectors.toList());

        Page<JSONObject> r = PageUtils.buildPage(req, list);

        return Result.success(r);
    }

    /**
     * 房屋账单数据统计
     */
    @ReqMapping("/house/bill/statistics")
    public Object houseBillStatistics(Token token, JSONObject req) throws Exception {

        JSONObject house = basicClient.getHouseById(token, req.getString("houseNo"));
        if (Objects.isNull(house)) {
            return Result.error(0, "不存在的房屋");
        }

        JSONObject community = basicClient.getCommunityById(token, house.getString("communityNo"));

        // 存在账单的时间范围
        JSONObject billTimeRange = getBillShouldPayDateTimeRange(token);

        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .eq(req, "houseNo", "paidState")
                .eq("enableState", BillEnableState.ENABLE)

                .maxEq("shouldPayDate", billTimeRange.getDate("minDate"))
                .minEq("shouldPayDate", Converter.toLocalDate(billTimeRange.getDate("maxDate")).plusDays(10))

                .eq("deleted", false)
                .exe();

        JSONObject r = new JSONObject();

        if (Objects.nonNull(community)) {
            r.put("communityName", community.getString("name"));
        }
        r.put("house", house);
        r.put("billsCount", bills.size());

        BigDecimal shouldReceive = chargeBillService.sumTotal(bills);
        r.put("billsTotal", shouldReceive);

        return Result.success(r);
    }

    /**
     * 账单列表
     */
    @ReqMapping("/bill/list")
    public Object listBill(JSONObject req) {

        FindArray<ChargeBill> find = jdbcSession.findArray(ChargeBill.class)
                .eq(req, "houseNo", "paidState")
                .eq("enableState", BillEnableState.ENABLE)
                .eq("deleted", false);

        JDBCUtil.between(req, find, "shouldPayDate");

        List<ChargeBill> bills = find.exe();

        for (ChargeBill bill : bills) {
            if (bill.getShouldPayDate() != null) {
                bill.setBillYear(bill.getShouldPayDate().getYear());
                bill.setBillMonth(bill.getShouldPayDate().getMonthValue());
            }
        }

        return Result.success(bills);
    }

    /**
     * app账单列表
     * 按月归类
     */
    @ReqMapping("/bill/group_list")
    public Object groupListBill(Token token, JSONObject req) {

        // 存在账单的时间范围
        JSONObject billTimeRange = getBillShouldPayDateTimeRange(token);

        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .eq(req, "houseNo", "paidState")
                .eq("enableState", BillEnableState.ENABLE)
                .maxEq("shouldPayDate", billTimeRange.getDate("minDate"))
                .minEq("shouldPayDate", Converter.toLocalDate(billTimeRange.getDate("maxDate")).plusDays(10))
                .eq("deleted", false)
                .order("shouldPayDate", "ASC").exe();

        Map<String, List<ChargeBill>> groupMap = new HashMap<>();

        for (ChargeBill bill : bills) {
            if (bill.getShouldPayDate() != null) {
                String year = String.valueOf(bill.getShouldPayDate().getYear());

                String month = bill.getShouldPayDate().getMonthValue() > 9
                        ? String.valueOf(bill.getShouldPayDate().getMonthValue())
                        : "0" + bill.getShouldPayDate().getMonthValue();

                bill.setBillYear(bill.getShouldPayDate().getYear());
                bill.setBillMonth(bill.getShouldPayDate().getMonthValue());

                List<ChargeBill> group = groupMap.get(year + "-" + month);
                if(null == group){
                    group = new ArrayList<>();
                }

                group.add(bill);
                groupMap.put(year + "-" + month, group);
            }
        }

        groupMap = sortMapByKey(groupMap);

        JSONArray resultJA = new JSONArray();
        if(Objects.nonEmpty(groupMap)){
            groupMap.forEach((k, v)->{
                BigDecimal total = chargeBillService.sumTotal(v);

                JSONObject billGroup = new JSONObject();

                String[] key = k.split("-");

                billGroup.put("year", key[0]);
                billGroup.put("month", key[1]);
                billGroup.put("bills", v);
                billGroup.put("total", total);
                resultJA.add(billGroup);
            });
        }

        return Result.success(resultJA);
    }

    /**
     * 使用 Map按key进行排序
     * @param map
     * @return
     */
    public static Map<String, List<ChargeBill>> sortMapByKey(Map<String, List<ChargeBill>> map) {
        if (map == null || map.isEmpty()) {
            return null;
        }

        Map<String, List<ChargeBill>> sortMap = new TreeMap<String, List<ChargeBill>>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });

        sortMap.putAll(map);

        return sortMap;
    }

    /**
     * 账单详情
     */
    @ReqMapping("/bill/get")
    public Object getBill(String id) {

        ChargeBill bill = Pool.get(ChargeBill.class, id);
        if (bill == null) {
            return Result.error(0, "账单不存在");
        }

        List<ChargeBillItem> billItems = jdbcSession.findArray(ChargeBillItem.class)
                .eq("billNo", bill.getId())
                .exe();
        bill.setBillItems(billItems);

        return Result.success(bill);
    }

    /**
     * 上传附件，账单置为已支付
     */
    @ReqMapping("/bill/annex/save")
    public Object saveAnnex(Token token, JSONObject req) {

        JSONArray js = req.getJSONArray("fileIds");
        List<String> fileIds = js.stream()
                .map(String::valueOf)
                .filter(StringUtil::isNotBlank)
                .collect(Collectors.toList());
        if (fileIds.isEmpty()) {
            return Result.error(0, "上传凭证不能为空");
        }

        ChargeBill bill = jdbcSession.findOne(ChargeBill.class)
                .eq(req, "id")
                .exe();
        if (bill == null) {
            return Result.error(0, "账单不存在");
        }

        if (bill.getPaidState() != BillPaidState.UNPAID) {
            return Result.error(0, "当前状态不可操作");
        }

        chargeBillAnnexService.doInsertBillAnnex(bill.getId(), fileIds, ChargeAnnexType.PAYMENT_VOUCHER);

        bill.setPaidState(BillPaidState.PAID);
        bill.setPaidAt(LocalDateTime.now());
        bill.setPayMethod(BillPayMethod.MANUAL);
        jdbcSession.updateById(bill);

        chargeLogService.saveLog(bill.getId(), BillOperateType.MANUAL_UPDATE_PAID_STATE, req, token.getUsername(), token.getUuid());

        return Result.success();
    }

    /**
     * 多选账单，上传附件，账单置为已支付
     */
    @ReqMapping("/bills/annex/save")
    public Object saveBillsAnnex(Token token, JSONObject req) {
        return chargeBillService.batchSaveAnnex(req, token);
    }

    /**
     * 选择账单，生成支付二维码
     */
    @ReqMapping("/bill/pay/qr_code")
    public Object createWxUnifiedOrderQrCode(Token token, HttpServletRequest requset, JSONObject req) throws Exception {

        List<ChargeBill> bills = jdbcSession.findArray(ChargeBill.class)
                .in("id", req.getJSONArray("ids"))
                .eq("deleted", false)
                .exe();

        // 传入id及查出记录数量比对
        if (bills.size() != req.getJSONArray("ids").size()) {
            return Result.error(0, "账单不存在");
        }

        List<String> houseNos = bills.stream().map(ChargeBill::getHouseNo)
                .distinct()
                .collect(Collectors.toList());
        if (houseNos.size() != 1) {
            return Result.error(0, "非同一房屋的账单");
        }
        final String houseNo = houseNos.get(0);

        for (ChargeBill bill : bills) {
            if (bill.getPaidState() == BillPaidState.PAID) {
                return Result.error(0, "账单已支付");
            }
            if (bill.getPaidState() != BillPaidState.UNPAID) {
                return Result.error(0, "当前状态不可支付");
            }
            if (bill.getTotal().compareTo(BigDecimal.ZERO) <= 0) {
                return Result.error(0, "账单异常：账单金额须大于0元");
            }
            if (CalculatorUtils.getDecimalPlace(bill.getTotal()) > 2) {
                return Result.error(0, "账单异常：金额最多支持2位小数");
            }
        }

        BigDecimal amt = chargeBillService.sumTotal(bills);
        if (amt.compareTo(BigDecimal.ZERO) <= 0) {
            return Result.error(0, "总金额必须大于0");
        }

        JSONObject house = basicClient.getHouseById(token, houseNo);
        if (Objects.isNull(house)) {
            return Result.error(0, "房屋不存在");
        }

        JSONObject community = basicClient.getCommunityById(token, house.getString("communityNo"));

        ChargeRecord cr = Pool.newInstance(ChargeRecord.class);
        cr.setCommunityNo(community.getString("id"));
        cr.setCommunityName(community.getString("name"));
        cr.setHouseNo(house.getString("id"));
        cr.setAmt(amt);

        chargeRecordService.setDepartmentNo(cr, token.getCompanyNo());

        cr.setTradeType("NATIVE");
        cr.setBizType(WaterSource.WUYEFEI);

        CallBackReturn<JSONObject> called = ()->{
            JSONObject json = new JSONObject();
            json.put("merge", true);
            return chargeRecordService.createUnifiedOrderByChargeRecord(token, requset, cr, "物业费支付", json.toJSONString(),"wxpay_web");
        };

        JSONObject result = Locks.exeSyn(house.getString("id"), called);

        if (result.getInteger("code") == 200) {
            // 微信统一下单成功 关联充值记录到缴费任务
            for (ChargeBill bill : bills) {
                bill.setChargeRecordId(cr.getId());
                jdbcSession.updateById(bill);
                // 创建订单关联 用于回调
                chargeBillService.insertChargeRecordRel(cr.getId(), ChargeRecordRelType.CHARGE_BILL, bill.getId());
            }
        }

        return result;
    }

    /**
     * 临时收费，生成支付二维码
     */
    @Bean(newInstance=true, copy=true)
    @ReqMapping("/temporary/pay/qr_code")
    public Object createTemporaryQrCode(Token token, HttpServletRequest requset,  ChargeRecordTemporary chargeRecordTemporary) throws Exception {
        if (chargeRecordTemporary.getTotal().compareTo(BigDecimal.ZERO) <= 0) {
            return Result.error(0, "总金额必须大于0");
        }

        ChargeRecord cr = Pool.newInstance(ChargeRecord.class);
        cr.setCommunityNo(chargeRecordTemporary.getCommunityNo());
        cr.setCommunityName(chargeRecordTemporary.getCommunityName());
        cr.setAmt(chargeRecordTemporary.getTotal());

        chargeRecordService.setDepartmentNo(cr, token.getCompanyNo());

        cr.setTradeType("NATIVE");
        cr.setBizType(WaterSource.TEMPORARY);

        CallBackReturn<JSONObject> called = ()->{

            JSONObject json = new JSONObject();
            json.put("companyNo", token.getCompanyNo());
            json.put("userId", token.getUserId());

            return chargeRecordService.createUnifiedOrderByChargeRecord(token,
                    requset,
                    cr,
                    chargeRecordTemporary.getCommunityName() + "-" + chargeRecordTemporary.getChargeFreeTypeName(),
                    json.toJSONString(),"wxpay_web");
        };

        JSONObject result = Locks.exeSyn(token.getUuid(), called);

        if (result.getInteger("code") == 200) {
            // 微信统一下单成功，记录相关信息
            chargeRecordTemporary.setChargeRecordId(cr.getId());
            jdbcSession.insert(chargeRecordTemporary);
        }

        return result;
    }

    /**
     * 向房屋账户手动充值
     * 增加房屋余额
     */
    @ReqMapping("/house/acc/charge")
    public Object chargeHouseAccount(Token token, JSONObject req) throws Exception {
        BigDecimal amt = req.getBigDecimal("amt");
        if (amt == null) {
            return Result.error(0, "请输入充值金额");
        }
        if (amt.compareTo(BigDecimal.ZERO) <= 0) {
            return Result.error(0, "金额须大于零");
        }
        if (CalculatorUtils.getDecimalPlace(amt) > 2) {
            return Result.error(0, "金额小数位最多支持2位");
        }
        // 附件
        JSONArray js = req.getJSONArray("fileIds");
        List<String> fileIds = js.stream()
                .map(String::valueOf)
                .filter(StringUtil::isNotBlank)
                .collect(Collectors.toList());
        if (fileIds.isEmpty()) {
            return Result.error(0, "上传凭证不能为空");
        }

        JSONObject house = basicClient.getHouseById(token, req.getString("id"));
        if (Objects.isNull(house)) {
            return Result.error(0, "不存在的房屋id");
        }

        JSONObject community = basicClient.getCommunityById(token, house.getString("communityNo"));

        ChargeRecord cr = Pool.newInstance(ChargeRecord.class);
        cr.setCommunityNo(community.getString("id"));
        cr.setCommunityName(community.getString("name"));
        cr.setHouseNo(house.getString("id"));

        chargeRecordService.setDepartmentNo(cr, token.getCompanyNo());
        cr.setMbrCode(token.getUuid());
        cr.setMobile(token.getMobile());
        cr.setMbrName(token.getUsername());

        cr.setAmt(amt);
        cr.setResult(true);
        cr.setChargeTime(LocalDateTime.now());
        cr.setBizType(WaterSource.CHARGE_BY_CASH);
        cr.setRemark("操作终端：物业MH5端");
        jdbcSession.insert(cr);

        ChargeAnnex ca = Pool.newInstance(ChargeAnnex.class);
        ca.setEntityType(ChargeAnnexEntityType.CHARGE_RECORD);
        ca.setEntityId(cr.getId());
        ca.setType(ChargeAnnexType.MANUAL_CHARGE_VOUCHER);
        ca.setFileIds(fileIds);
        jdbcSession.insert(ca);

        //充值操作
        return accoutService.manualIncreHouseAccount(cr, token.getUuid());

    }

    /**
     * 房屋账户详情
     */
    @ReqMapping("/house/account/get")
    public Object getHouseAccount(Token token, JSONObject req) throws Exception {
        JSONObject house = basicClient.getHouseById(token, req.getString("houseNo"));
        if (Objects.isNull(house)) {
            return Result.error(0, "不存在的房屋id");
        }

        JSONObject community = basicClient.getCommunityById(token, house.getString("communityNo"));

        // 房屋账单获取地址
        Account account = accoutService.getHouseAccount(house.getString("id"));

        JSONObject r = new JSONObject();
        r.put("communityName", community.getString("name"));
        r.put("buildingName", house.getString("buildName"));
        r.put("unitName", house.getString("unitName"));
        r.put("name", house.getString("name"));
        r.put("useAmt", CalculatorUtils.formatMoney(account.getUseAmt()));

        return Result.success(r);
    }

    /**
     * 根据房屋编号下载收据
     * @param req 参数值
     * @param req 类型：1、根据billNo查询单条记录，
     *                  2、根据chargeRecordId查询批次集合数据，
     *                  3、根据时间2021-06查询本月集合数据
     */
    @ReqMapping("/receipt/download")
    public Object receiptDownLoad(Token token, JSONObject req) throws Exception {

        String orderId = req.getString("orderNo");
        String orderType = req.getString("orderType");

        logger.info("调用PDF文件接口下载，orderId：{}，orderType：{}",orderId,orderType);

        if (Objects.isNull(orderId) || Objects.isNull(orderType)) {
            return Result.error(0, "订单id不存在");
        }

        JSONObject js = new JSONObject();

        //1.根据billNO查询单条数据
        if(orderType.equals("1")){
            ReceiptOrder receiptOrder = jdbcSession.findOne(ReceiptOrder.class).eq("payOrderNo",orderId).exe();
            if(null == receiptOrder ){
                return Result.error(0, "账单billNo不存在！");
            }else{
                js.put("list", receiptOrder);
            }
        }else if(orderType.equals("2")){
            //2.根据支付成功订单id查询所有收据
            List<ChargeBill> receiptList = jdbcSession.findArray(ChargeBill.class).eq("chargeRecordId",orderId).exe();
            if(null == receiptList){
                return Result.error(0, "批次订单号不存在！");
            }

            js.put("list", receiptList);

        }else if(orderType.equals("3")){
            //3.根据年月份查询所有收据 (根据应付日期: 2021-06)
            List<ChargeBill> receiptList = jdbcSession.findArray(ChargeBill.class)
                    .like("shouldPayDate",orderId)
                    .eq("houseNo",req.getString("houseNo"))
                    .isNotEmpty("fileId").exe();
            if(null == receiptList){
                return Result.error(0, "批次订单号不存在！");
            }

            js.put("list", receiptList);
        }

        return Result.success(js);
    }

}
